{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "91e01315",
   "metadata": {},
   "source": [
    "在 LangChain 生态中，Document Loaders（文档加载器） 是负责从多样化外部数据源中读取数据，并将其统一转换为 LangChain 标准 Document 对象的核心组件。它解决了 “LLM/RAG 应用如何接入不同格式、不同来源数据” 的基础问题，是数据从 “原始存储” 流向 “后续处理（文本分割、嵌入、检索）” 的第一环节，也是构建外部知识增强型应用（如嵌入式芯片手册问答、硬件日志分析）的关键基础。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8090cf63",
   "metadata": {},
   "source": [
    "# 一、核心定义与本质\n",
    "根据 LangChain 官方文档，Document Loaders 的核心职责是：\n",
    "将非结构化 / 结构化数据（如 PDF 手册、网页教程、CSV 配置表、硬件日志文件等）从原始来源（本地文件、网络地址、数据库等）加载，并输出为 LangChain 统一的 Document 数据结构 —— 每个 Document 对象包含两个核心字段：\n",
    "\n",
    "content：原始数据的文本内容（如 PDF 某页的文字、网页正文）；\n",
    "metadata：数据的附加描述信息（如文件路径、PDF 页码、网页 URL、加载时间等），用于后续检索时的筛选（如 “只查询 STM32F103 手册的第 10-20 页”）。\n",
    "\n",
    "其本质是 **“数据适配器”**：无论数据源格式（PDF/HTML/CSV）、存储位置（本地 / 网络）如何，都通过统一接口输出标准化数据，为后续文本分割（Text Splitters）、向量嵌入（Embedding Models）等组件提供一致输入。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0d4dff7c",
   "metadata": {},
   "source": [
    "# 二、支持的核心数据源（官方文档覆盖场景）\n",
    "LangChain v0.3 的 Document Loaders 覆盖了开发者常用的几乎所有数据来源，尤其适配嵌入式开发中可能用到的 “技术文档、配置文件、日志数据” 等场景，官方文档明确列出的核心加载场景包括：\n",
    "\n",
    "数据源类型|\t具体场景（官方 How-to 覆盖）|\t嵌入式开发关联示例|\n",
    "|:--|:--|:--\n",
    "本地文件|\t加载 PDF 文件（如芯片手册）|\t加载 STM32F103 数据手册 PDF，提取寄存器配置章节\n",
    "-|加载 Markdown 文件（如开发笔记）\t|加载嵌入式项目的 README.md 调试指南\n",
    "-|加载 Microsoft Office 文件（如 Excel 配置表）\t|加载硬件测试数据的 Excel 报表\n",
    "-|加载 JSON/CSV 数据（如外设参数表）\t|加载传感器校准参数的 CSV 文件\n",
    "-|加载 HTML 文件（如本地保存的技术博客）\t|加载本地缓存的 “STM32 DMA 配置教程” HTML\n",
    "网络资源|\t加载网页内容（如在线芯片手册、技术论坛）\t|爬取 TI 官网的 ADC 芯片应用笔记网页\n",
    "批量数据|\t加载指定目录下的所有文件（如多份日志文件）\t|加载某项目 “debug_logs” 目录下的所有硬件日志"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0f30af3f",
   "metadata": {},
   "source": [
    "# 示例\n",
    "## 加载PDF文档"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8026cdae",
   "metadata": {},
   "outputs": [],
   "source": [
    "%pip install pypdf"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "40ab1c5c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'producer': '', 'creator': 'WPS 文字', 'creationdate': '2025-08-05T14:58:13+08:00', 'author': '郑港', 'comments': '', 'company': '', 'keywords': '', 'moddate': '2025-08-05T14:58:13+08:00', 'sourcemodified': \"D:20250805145813+08'00'\", 'subject': 'CMMI体系文件', 'title': '北汽新能源C46远程诊断终端接口说明', 'trapped': '/False', 'source': '../../test.pdf', 'total_pages': 41, 'page': 0, 'page_label': '1'}\n",
      "\n",
      "北汽新能源 C46 远程诊断项目\n",
      "TBOX 集成规范\n",
      "Ver.2.4\n"
     ]
    }
   ],
   "source": [
    "from langchain_community.document_loaders import PyPDFLoader\n",
    "\n",
    "file_path = (\"../../test.pdf\")\n",
    "\n",
    "loader = PyPDFLoader(file_path)\n",
    "pages = [] \n",
    "async for page in loader.alazy_load():\n",
    "    pages.append(page)\n",
    "print(f\"{pages[0].metadata}\\n\")\n",
    "print(pages[0].page_content)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "eb81c93d",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "d:\\_python_env\\x_project\\llm\\venv\\Lib\\site-packages\\tqdm\\auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
      "  from .autonotebook import tqdm as notebook_tqdm\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Page 20: {\n",
      "uint8_t taskid[64]; /* 任务 id */\n",
      "uint8_t path[128]; /* 打包后日志文件存放路径 */\n",
      "uint8_t type; /* 压缩方式。 1：zip 2：tar */\n",
      "uint8_t encrypt； /* 是否加密 0：不加密， 1：加密 默认不加密 */\n",
      "}__attribute__((packed)) RMD_API_TBX_LOG_T;\n",
      "5.2.2.13 rmd_api_tbox_log_trigger TBOX 日志文件上传触发\n",
      "接口定义 RMD_API_ERR rmd_api_tbox_log_trigger(RMD_API_TBX_LOG_TRIG_T *info)\n",
      "接口实现者 RMD APP\n",
      "接口调用者 TBOX\n",
      "输入参数\n",
      "参数代码 参数名称 数据类型 是否\n",
      "必填 参数说明\n",
      "info 日志文件打\n",
      "包信息\n",
      "RMD_API_TBX\n",
      "_LOG_TRIG_T 是 见下表定义\n",
      "返回信息 接口响应码 RMD_API_OK ：成功，其他：失败 详见 5.1 错误码定义\n",
      "TBOX日志获取参数原型\n",
      "typedef struct\n",
      "{\n",
      "uint8_t taskid[64]; /* 任务 id */\n",
      "uint8_t name[128]; /* 打包后日志文件名 */\n",
      "uint8_t type; /* 压缩方式。 1：zip 2：tar */\n",
      "uint8_t encrypt； /* 是否加密 0：不加密， 1：加密 默认不加密 */\n",
      "}__attribute__((packed)) RMD_API_TBX_LOG_TRIG_T;\n",
      "【TBOX日志文件获取，接口调用及处理逻辑如下】：\n",
      "\n",
      "Page 19: 返回信息 接口响应码 RMD_API_OK ：成功，其他：失败 详见 5.1 错误码定义\n",
      "注·对应 C46DB 车型的矩阵信号为：\n",
      "报文名称：TBOX_0x4F4\n",
      "信号名称：TBOX_RemKL15PwrOnReq\n",
      "起始位：30，终止位：31\n",
      "取值：\n",
      "0x0: 初始值\n",
      "0x1: 有请求\n",
      "0x2: 无请求\n",
      "0x3: Void\n",
      "注意：该信号值可能会被其它业务(如 OTA/远控等)使用，所以在设置该信号时 TBOX 需要遵守以下逻辑：\n",
      "远程诊断\n",
      "其它业务\n",
      "0-初始值 1-有请求 2-无请求\n",
      "0-初始值 0 1 2\n",
      "1-有请求 1 1 1\n",
      "2-无请求 2 1 2\n",
      "5.2.2.11 rmd_api_event_trigger TBOX状态通知\n",
      "接口定义 RMD_API_ERR rmd_api_event_trigger(RMD_API_EVENT_M event)\n",
      "接口实现者 RMD APP\n",
      "接口调用者 TBOX\n",
      "输入参数\n",
      "参数代码 参数名称 数据类型 是否\n",
      "必填 参数说明\n",
      "event 触发事件 RMD_API_EVE\n",
      "NT_M 是\n",
      "见 5.2.3 枚 举 类 型\n",
      "RMD_API_EVENT_M定义\n",
      "返回信息 接口响应码 RMD_API_OK ：成功，其他：失败 详见 5.1 错误码定义\n",
      "5.2.2.12 rmd_api_get_tbox_log 获取 TBOX 日志文件\n",
      "接口定义 RMD_API_ERR rmd_api_get_tbox_log(RMD_API_TBX_LOG_T *info)\n",
      "接口实现者 TBOX\n",
      "接口调用者 RMD APP\n",
      "输入参数\n",
      "参数代码 参数名称 数据类型 是否\n",
      "必填 参数说明\n",
      "info 获取日志文\n",
      "件信息\n",
      "RMD_API_TBX\n",
      "_LOG_T 是 见下表定义\n",
      "返回信息 接口响应码 RMD_API_OK ：成功，其他：失败 详见 5.1 错误码定义\n",
      "TBOX日志获取参数原型\n",
      "typedef struct\n",
      "\n"
     ]
    }
   ],
   "source": [
    "from langchain_core.vectorstores import InMemoryVectorStore\n",
    "from langchain_huggingface import HuggingFaceEmbeddings\n",
    "\n",
    "# 使用通用开源嵌入模型(免费)，deepseek未提供嵌入模型，所以我们选择HuggingFace的开源模型\n",
    "embeddings = HuggingFaceEmbeddings(\n",
    "    model_name = \"../../embed_model/bge-large-zh-v1.5\",  # 首次会下载该模型（这里我们提前手动下载好了）\n",
    "    model_kwargs = {\"device\": \"cpu\"},  # 可指定\"cuda\"使用GPU,\n",
    "    encode_kwargs = {\"normalize_embeddings\": True}  # 归一化向量，提升相似度计算准确性\n",
    ")\n",
    "\n",
    "vector_store = InMemoryVectorStore.from_documents(pages, embeddings)\n",
    "docs = vector_store.similarity_search(\"获取 TBOX 日志文件\", k=2)\n",
    "for doc in docs:\n",
    "    print(f\"Page {doc.metadata['page']}: {doc.page_content}\\n\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "aeff665f",
   "metadata": {},
   "source": [
    "## 批量加载目录下的所有日志文件\n",
    "嵌入式调试中常生成多份 TXT 日志文件，可使用 DirectoryLoader 批量加载："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "624ea467",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 1. 无需额外依赖（基础文件加载已包含在 langchain_community 中）\n",
    "from langchain_community.document_loaders import DirectoryLoader\n",
    "\n",
    "# 2. 初始化目录加载器（指定目录、筛选文件类型）\n",
    "loader = DirectoryLoader(\n",
    "    path=\"./debug_logs\",  # 日志文件所在目录\n",
    "    glob=\"*.txt\",         # 只加载 .txt 格式的日志文件\n",
    "    show_progress=True    # 显示加载进度\n",
    ")\n",
    "\n",
    "# 3. 批量加载所有符合条件的文件\n",
    "documents = loader.load()\n",
    "\n",
    "# 4. 查看结果（每个日志文件对应一个 Document，metadata 包含文件路径）\n",
    "print(f\"批量加载的日志文件数：{len(documents)}\")\n",
    "print(f\"第一个日志文件的路径：{documents[0].metadata['source']}\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "venv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
